/*
 * JFugue - API for Music Programming
 * Copyright (C) 2003-2008  David Koelle
 *
 * http://www.jfugue.org 
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *  
 */

package org.jfugue;

import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;

/**
 * Provides a player that can be given new MusicStrings at runtime. The original
 * Player class requires that a MusicString be fully formed before sending to
 * Player.play(). This class lets you add new bits of a MusicString with the
 * add() method. The newly-added patterns are played immediately.
 *
 * @see Pattern
 * @see Player
 * @author David Koelle
 * @version 3.2
 */
public final class StreamingPlayer {
	private Sequencer sequencer;
	private StreamingMidiRenderer renderer;
	private MusicStringParser parser;
	private DurationPatternTool durationPatternTool;

	/**
	 * Instantiates a new Player object, which is used for playing music.
	 */
	public StreamingPlayer() {
		try {
			init(MidiSystem.getSequencer());
		} catch (MidiUnavailableException e) {
			throw new JFugueException(
					JFugueException.SEQUENCER_DEVICE_NOT_SUPPORTED_WITH_EXCEPTION
							+ e.getMessage());
		}
	}

	/**
	 * Creates a new StreamingPlayer instance using a Sequencer that you have
	 * provided.
	 * 
	 * @param sequencer
	 *            The Sequencer to send the MIDI events
	 */
	public StreamingPlayer(Sequencer sequencer) {
		init(sequencer);
	}

	/**
	 * Creates a new StreamingPlayer instance using a Sequencer obtained from
	 * the Synthesizer that you have provided.
	 * 
	 * @param synth
	 *            The Synthesizer you want to use for this Player.
	 */
	public StreamingPlayer(Synthesizer synth) throws MidiUnavailableException {
		this(Player.getSequencerConnectedToSynthesizer(synth));
	}

	private void init(Sequencer sequencer) {
		setSequencer(sequencer);

		parser = new MusicStringParser();
		renderer = new StreamingMidiRenderer();
		parser.addParserListener(renderer);

		durationPatternTool = new DurationPatternTool();
		parser.addParserListener(durationPatternTool);
	}

	/**
	 * Closes MIDI resources - be sure to call this after play() has returned.
	 */
	public void close() {
		getSequencer().close();
		try {
			if (MidiSystem.getSynthesizer() != null) {
				MidiSystem.getSynthesizer().close();
			}
		} catch (MidiUnavailableException e) {
			throw new JFugueException(
					JFugueException.GENERAL_ERROR + e.getMessage());
		}
		renderer.close();
	}

	private void setSequencer(Sequencer sequencer) {
		this.sequencer = sequencer;
	}

	/**
	 * Returns the sequencer containing the MIDI data from a pattern that has
	 * been parsed.
	 * 
	 * @return the Sequencer from the pattern that was recently parsed
	 */
	public Sequencer getSequencer() {
		return this.sequencer;
	}

	/**
	 * Streams a Pattern containing a single token. Does not wait for the music
	 * to stop playing - the user will need to throttle calls to stream().
	 * 
	 * @param singleToken
	 */
	public void stream(Pattern singleToken) {
		parser.parse(singleToken);
	}

	/**
	 * Streams a MusicString containing a single token. Does not wait for the
	 * music to stop playing - the user will need to throttle calls to stream().
	 * 
	 * @param singleToken
	 */
	public void stream(String singleToken) {
		stream(new Pattern(singleToken));
	}

	/**
	 * Streams a Pattern containing one or more tokens, and waits for each token
	 * to finish playing before returning. NOTE: The timing logic is a bit off
	 * TODO: The timing logic in streamAndWait() needs to be fixed
	 * 
	 * @param fragment
	 */
	public void streamAndWait(Pattern fragment) {
		String[] tokens = fragment.getTokens();
		for (String token : tokens) {
			durationPatternTool.reset();
			Pattern pattern = new Pattern(token);
			parser.parse(pattern);
			long duration = durationPatternTool.getDuration();
			try {
				System.out.println(duration);
				Thread.sleep(duration);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Streams a MusicString containing one or more tokens, and waits for each
	 * token to finish playing before returning. NOTE: The timing logic is a bit
	 * off
	 * 
	 * @param fragment
	 */
	public void streamAndWait(String fragment) {
		streamAndWait(new Pattern(fragment));
	}

}
